﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Net;
using System.ComponentModel;

namespace Alex.Library.Net.Communications
{
    /// <summary>
    /// DHCP相关的Helper类
    /// </summary>
    public class DhcpHelper
    {
        /// <summary>
        /// 获取子网地址
        /// </summary>
        /// <param name="serverIpAddress">服务器地址</param>
        /// <returns></returns>
        public static IEnumerable<IPAddress> EnumSubnets(string serverIpAddress)
        {
            UInt32 DHCPResult = 0;
            IntPtr ips;
            uint nr = 0;
            uint total = 0;
            uint resumeHandle = 0;
            List<IPAddress> ipAddressArray = new List<IPAddress>();

            do
            {
                DHCPResult = DhcpEnumSubnets(serverIpAddress, ref resumeHandle, 1000, out ips, ref nr, ref total);
                if (DHCPResult == ERROR_SUCCESS)
                {
                    DHCP_IP_ARRAY ipArray = (DHCP_IP_ARRAY)Marshal.PtrToStructure(ips, typeof(DHCP_IP_ARRAY));
                    int size = (int)ipArray.NumElements;
                    IntPtr outArray = ipArray.Elements;
                    IntPtr current = outArray;
                    DHCP_IP_ADDRESS ipAddress;

                    for (int i = 0; i < size; i++)
                    {
                        ipAddress = new DHCP_IP_ADDRESS();
                        Marshal.PtrToStructure(current, ipAddress);
                        Marshal.DestroyStructure(current, typeof(DHCP_IP_ADDRESS));
                        current = (IntPtr)((int)current + Marshal.SizeOf(ipAddress));
                        ipAddressArray.Add(IPAddress.Parse(UInt32IPAddressToString(ipAddress.IPAddress)));
                    }
                    Marshal.FreeCoTaskMem(outArray);
                }
                else if (DHCPResult != ERROR_NO_MORE_ITEMS)
                {
                    Win32Exception winex = new Win32Exception((int)DHCPResult);
                    throw winex;
                }
            } while (total != 0);

            return ipAddressArray;
        }
        
        /// <summary>
        /// 获取子网中的客户端信息
        /// </summary>
        /// <param name="serverIpAddress">服务器地址</param>
        /// <param name="subnet">子网地址</param>
        /// <returns></returns>
        public static IEnumerable<CUSTOM_CLIENT_INFO> EnumClients(string serverIpAddress, IPAddress subnet)
        {
            UInt32 DHCPResult = 0;
            IntPtr ips;
            uint SubnetMask = IPAddressToUInt32(subnet);
            uint nr = 0;
            uint total = 0;
            uint resumeHandle = 0;
            List<CUSTOM_CLIENT_INFO> clientInfoArray = new List<CUSTOM_CLIENT_INFO>();

            do
            {
                DHCPResult = DhcpEnumSubnetClients(serverIpAddress, SubnetMask, ref resumeHandle, 65536, out ips, ref nr, ref total);
                var cli = Marshal.PtrToStructure(ips, typeof(DHCP_CLIENT_INFO_ARRAY));
                if (null == cli) continue;
                DHCP_CLIENT_INFO_ARRAY clients = (DHCP_CLIENT_INFO_ARRAY)cli;
                // DHCP_CLIENT_INFO_ARRAY clients = (DHCP_CLIENT_INFO_ARRAY)Marshal.PtrToStructure(ips, typeof(DHCP_CLIENT_INFO_ARRAY));

                int size = (int)clients.NumElements;
                IntPtr[] ptr_array = new IntPtr[size];
                IntPtr current = clients.Clients;

                for (int i = 0; i < size; i++)
                {
                    ptr_array[i] = Marshal.ReadIntPtr(current);
                    current = (IntPtr)((int)current + (int)Marshal.SizeOf(typeof(IntPtr)));
                }

                for (int i = 0; i < size; i++)
                {
                    DHCP_CLIENT_INFO curr_element = (DHCP_CLIENT_INFO)Marshal.PtrToStructure(ptr_array[i], typeof(DHCP_CLIENT_INFO));
                    CUSTOM_CLIENT_INFO clientInfo = new CUSTOM_CLIENT_INFO();

                    clientInfo.IpAddress = UInt32IPAddressToString(curr_element.ClientIpAddress);
                    clientInfo.ClientName = curr_element.ClientName;
                    clientInfo.MacAddress = String.Format("{0:x2}:{1:x2}:{2:x2}:{3:x2}:{4:x2}:{5:x2}",
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data),
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data, 1),
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data, 2),
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data, 3),
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data, 4),
                        Marshal.ReadByte(curr_element.ClientHardwareAddress.Data, 5));

                    clientInfoArray.Add(clientInfo);
                }
            } while (DHCPResult == ERROR_MORE_DATA);

            if (DHCPResult != ERROR_SUCCESS)
            {
                Win32Exception winex = new Win32Exception((int)DHCPResult);
                throw winex;
            }

            return clientInfoArray;
        }

        private static string UInt32IPAddressToString(uint ipAddress)
        {
            IPAddress ipA = new IPAddress(ipAddress);
            string[] sIp = ipA.ToString().Split('.');

            return sIp[3] + "." + sIp[2] + "." + sIp[1] + "." + sIp[0];
        }

        #region Native

        const uint ERROR_SUCCESS = 0;
        const uint ERROR_MORE_DATA = 234;
        const uint ERROR_NO_MORE_ITEMS = 259;

        public static uint IPAddressToUInt32(IPAddress ipAddress)
        {
            byte[] ipbytes = ipAddress.GetAddressBytes();
            uint ipuint = (uint)ipbytes[0] << 24;
            ipuint += (uint)ipbytes[1] << 16;
            ipuint += (uint)ipbytes[2] << 8;
            ipuint += (uint)ipbytes[3];
            return ipuint;
        }


        [DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern uint DhcpEnumSubnets(
            string ServerIpAddress,
            ref uint ResumeHandle,
            uint PreferredMaximum,
            out IntPtr EnumInfo,
            ref uint ElementsRead,
            ref uint ElementsTotal
        );

        [DllImport("dhcpsapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern uint DhcpEnumSubnetClients(
            string ServerIpAddress,
            uint SubnetAddress,
            ref uint ResumeHandle,
            uint PreferredMaximum,
            out IntPtr ClientInfo,
            ref uint ElementsRead,
            ref uint ElementsTotal);

        [StructLayout(LayoutKind.Sequential)]
        public struct DHCP_IP_ARRAY
        {
            public uint NumElements;
            public IntPtr Elements;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public class DHCP_IP_ADDRESS
        {
            public UInt32 IPAddress;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DHCP_CLIENT_INFO_ARRAY
        {
            public uint NumElements;
            public IntPtr Clients;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DHCP_CLIENT_UID
        {
            public uint DataLength;
            public IntPtr Data;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct DATE_TIME
        {
            public uint dwLowDateTime;
            public uint dwHighDateTime;

            public DateTime Convert()
            {
                if (dwHighDateTime == 0 && dwLowDateTime == 0)
                {
                    return DateTime.MinValue;
                }
                if (dwHighDateTime == int.MaxValue && dwLowDateTime == UInt32.MaxValue)
                {
                    return DateTime.MaxValue;
                }
                return DateTime.FromFileTime((((long)dwHighDateTime) << 32) | (UInt32)dwLowDateTime);
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct DHCP_HOST_INFO
        {
            public uint IpAddress;
            public string NetBiosName;
            public string HostName;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct DHCP_CLIENT_INFO
        {
            public uint ClientIpAddress;
            public uint SubnetMask;
            public DHCP_CLIENT_UID ClientHardwareAddress; //no pointer -> structure !!
            [MarshalAs(UnmanagedType.LPWStr)]
            public string ClientName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string ClientComment;
            public DATE_TIME ClientLeaseExpires; //no pointer -> structure !!
            public DHCP_HOST_INFO OwnerHost; //no pointer -> structure
        }

        public struct CUSTOM_CLIENT_INFO
        {
            public string ClientName;
            public string IpAddress;
            public string MacAddress;
        }

        #endregion
    }
}
